#include "kstdio.h"
#include "kmm.h"
#include "fs.h"
#include "ifs.h"
#include "process.h"
#include "security.h"
#include "config.h"
#include "dbgio.h"


int kmount(char* mp,char* dev,char *type){
	vfs_node_t *mp_n;
	vfs_node_t *dev_n;
	mp_n = finddir_rc_fs(0,mp);
	dev_n = finddir_rc_fs(0,dev);
	if (mp_n == 0){
		get_current_process()->errno = ENOENT;
		return 0;
	}
	if (dev_n == 0){
		get_current_process()->errno = ENODEV;
		return 0;
	}
	return ifs_mount(mp_n,dev_n,type);
}

int kumount(char *mp,char *type){
	vfs_node_t *mp_n;
	mp_n = finddir_rc_fs(0,mp);
	if (mp_n == 0){
		get_current_process()->errno = ENOENT;
		return 0;
	}
	return ifs_umount(mp_n,type);
}

/**
 * Links the file instances described by two descriptors in seperate processes
 */
int link_fd(process_info_t *source,process_info_t *dest,KFILE source_fd,KFILE dest_fd){
	ILLIST *fd_backend;
	ILLIST *targ_fd = (ILLIST *) kmalloc(sizeof (ILLIST));
	fd_backend = illist_get(source->fd_table->fd_table,(int)source_fd);
	if (fd_backend == 0){
		get_current_process()->errno = EBADF;
		dprintf("Failed to link FD, Source PID:0x%x\n",source->pid);
		return 0;
	}
	targ_fd->value = fd_backend->value;
	targ_fd->index = dest_fd;
	((fileinfo_t *)targ_fd->value)->refs++;
	illist_add_end(dest->fd_table->fd_table,targ_fd);
	return 1;
}

/**
 * Creates one end of a nameless pipe, used for making initial STDIN/OUT/ERR before devfs is mounted
 */
void create_fd(KFILE fd,uint32_t flags,uint32_t inode,read_fs_t read_hook,write_fs_t write_hook){
	/* Allocate memory */
	vfs_node_t *node = (vfs_node_t *) kmalloc(sizeof(vfs_node_t));
	ILLIST *targ_fd = (ILLIST *) kmalloc(sizeof (ILLIST));
	fileinfo_t *file = (fileinfo_t *) kmalloc(sizeof(fileinfo_t));
	/* Clear memory */
	memset((void *)node,0,sizeof(vfs_node_t));
	/* Set up pipe */
	node->flags = VFS_FILE_FLAG | VFS_PIPE_FLAG;
	node->length = 0;
	node->read = read_hook;
	node->write = write_hook;
	node->inode = inode;
	/* "open" the pipe */
	file->node = node;
	file->oflags = flags;
	file->pos = 0;
	file->refs = 1;
	/* Add to fd table */
	targ_fd->value = (void *) file;
	targ_fd->index = (uint32_t) fd;
	illist_add_end(get_current_process()->fd_table->fd_table,targ_fd);
}

void initialize_filetable(process_info_t *p){
	LLIST *new_entry = (LLIST *) kmalloc(sizeof(LLIST));
	p->fd_table = (filetable_t *) kmalloc(sizeof(filetable_t));
	p->fd_table->freed_fds = (LLIST *) kmalloc(sizeof(LLIST));
	p->fd_table->fd_table = (ILLIST *) kmalloc(sizeof(ILLIST));
	p->fd_table->fd_alloc = 3;
	llist_create(p->fd_table->freed_fds);
	new_entry->value = (void *) -1;
	llist_add_end(p->fd_table->freed_fds,new_entry);
	illist_create(p->fd_table->fd_table);
	if (p->parent != 0){
		link_fd(p->parent,p,0,0);
		link_fd(p->parent,p,1,1);
		link_fd(p->parent,p,2,2);
	}
}

/**
 * Returns a free file descriptor
 */
KFILE getfreefd(){
	process_info_t *p = get_current_process();
	LLIST * first = llist_get_last(p->fd_table->freed_fds);
	int val = (int) first->value;
	if (val == -1)
		return (KFILE) (p->fd_table->fd_alloc++);
	else {
		first = llist_remove_last(p->fd_table->freed_fds);
		val = (int) first->value;
		kfree((void *) first);
		return (KFILE) val;
	}
}


int read (KFILE fd,uint8_t *buffer,uint32_t length){
	process_info_t *p = get_current_process();
	fileinfo_t *file;
	ILLIST *fd_backend;
	int read = -1;
	fd_backend = illist_get(p->fd_table->fd_table,(int)fd);
	if (fd_backend == 0){
		p->errno = EBADF;
		return -1;
	}
	file = (fileinfo_t *) fd_backend->value;
	if ((file->oflags & FREAD) == 0){
		p->errno = EACCES;
		return -1;
	}
	read = read_fs(file->node,buffer,file->pos,length,file->oflags);
	file->pos += read;
	return read;
}

int write(KFILE fd,uint8_t *buffer,uint32_t length){
	process_info_t *p = get_current_process();
	fileinfo_t *file;
	ILLIST *fd_backend;
	int read = -1;
	fd_backend = illist_get(p->fd_table->fd_table,(int)fd);
	//dprintf("write1 0x%x %i\n",fd_backend,length);
	if (fd_backend == 0){
		p->errno = EBADF;
		return -1;
	}
	file = (fileinfo_t *) fd_backend->value;
	//dprintf("write1. 0x%x %i\n",file,length);
	if ((file->oflags & FWRITE) == 0){
		dprintf("EACCES 0x%x %i\n",file->oflags,length);
		p->errno = EACCES;
		return -1;
	}
	//dprintf("write2 %i\n",length);
	read = write_fs(file->node,buffer,file->pos,length,file->oflags);
	file->pos += read;
	return read;
}

int seek (KFILE fd,uint32_t position, uint8_t whence){
	process_info_t *p = get_current_process();
	fileinfo_t *file;
	ILLIST *fd_backend;
	fd_backend = illist_get(p->fd_table->fd_table,(int)fd);
	if (fd_backend == 0){
		p->errno = EBADF;
		return -1;
	}
	file = (fileinfo_t *) fd_backend->value;
	switch (whence){
		case SEEK_SET:
			file->pos = position;
			break;
		case SEEK_CUR:
			file->pos += position;
			break;
		case SEEK_END:
			file->pos = file->node->length + position;
			break;
	}
	return file->pos;
}

KFILE open (char *name,uint32_t flags,uint32_t mode){
	process_info_t *p = get_current_process();
	ILLIST *fd_entry = (ILLIST *) kmalloc(sizeof(ILLIST));
	fileinfo_t *file = (fileinfo_t *) kmalloc(sizeof(fileinfo_t));
	file->node = finddir_rc_fs(0,name);
	if (file->node == 0){
		if (flags & O_CREAT){
			file->node = touch_rc_fs(0,name,get_file_mode(mode));
		} else {
			kfree((void *)file);
			kfree((void *)fd_entry);
			p->errno = ENOENT;
			dprintf("ENOENT %s\n",name);
			return -1;
		}
	} else if ((flags & O_CREAT) && (flags % O_EXCL)) {
			kfree((void *)file);
			kfree((void *)fd_entry);
			p->errno = EEXIST;
			return -1;
	}
	flags++;//turn into FCNTL mode
	flags &= to_fcntl(get_permissions(file->node));
	file->oflags = flags;
	file->refs = 1;
	if (flags & O_APPEND)
		file->pos = file->node->length;
	else
		file->pos = 0;
	open_fs(file->node);
	fd_entry->index = getfreefd();
	fd_entry->value = (void *) file;

	illist_add_end(p->fd_table->fd_table,fd_entry);
	return fd_entry->index;
}


int	close(KFILE fd){
	process_info_t *p = get_current_process();
	LLIST *new_entry;
	fileinfo_t *file;
	ILLIST *fd_backend;
	fd_backend = illist_get(p->fd_table->fd_table,(int)fd);
	if (fd_backend == 0){
		p->errno = EBADF;
		return 0;
	}
	file = (fileinfo_t *) fd_backend->value;
	illist_unlink(fd_backend);
	close_fs(file->node);
	kfree((void *)fd_backend);
	file->refs--;
	if (file->refs == 0)
		kfree(file);
	//Freeing FD
	if (fd == (p->fd_table->fd_alloc - 1))
		p->fd_table->fd_alloc--;
	else {
		new_entry = (LLIST *)kmalloc(sizeof(LLIST));
		new_entry->value = (void *) fd;
		llist_add_end(p->fd_table->freed_fds,new_entry);
	}
	return 1;
}

int chdir (char *name){
	struct vfs_node *node = finddir_rc_fs(0,name);
	if (node == 0){
		get_current_process()->errno = ENOENT;
		return -1;
	}
	get_current_process()->current_directory = node;
	return 0;
}

int mkdir (char *name){
	char fnbuf[CFG_FILENAME_LENGTH];
	struct vfs_node *node;
	strcpy(fnbuf,name);
	strcat(fnbuf,"/..");
	node = finddir_rc_fs(0,fnbuf);
	if (!(get_permissions(node) & 2)){
		get_current_process()->errno = EACCES;
		return -1;
	}
	node = mkdir_rc_fs(0,name,0x1FF);
	if (node == 0){
		get_current_process()->errno = ENOSYS;
		return -1;
	}
	return 0;
}

int		 lenf(KFILE fd){
	process_info_t *p = get_current_process();
	fileinfo_t *file;
	ILLIST *fd_backend;
	fd_backend = illist_get(p->fd_table->fd_table,(int)fd);
	if (fd_backend == 0){
		p->errno = EBADF;
		return -1;
	}
	file = (fileinfo_t *) fd_backend->value;
	return file->node->length;
}